-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal for new editors system #1166
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good for the most part, however I'd really like to reduce the API surface to a minimum before merging this in (instead of thinking about all the what-if use-cases).
One more major thing to address is the addition of core-quantity
peer dep.
I like the iconName
-> React.ReactNode
mapping for abstract iconSpec
types. However, let's do this in a separate PR.
import { useAuth } from "../frontend/Auth"; | ||
import { PageLayout } from "@itwin/itwinui-layouts-react"; | ||
import { SignInPage } from "../frontend/SignInPage"; | ||
import React from 'react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant formatter changes
import { appInitializer } from "../frontend/AppInitializer"; | ||
import { App } from "../frontend/App"; | ||
import { UiFramework } from "@itwin/appui-react"; | ||
import React from 'react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant formatter changes
editorPosition: { rowPriority: 21, columnIndex: 2 }, | ||
isDisabled: readonly, | ||
}); | ||
const angleDescription = new AngleDescription(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spreading an instance seems more dangerous (potentially due to getters?).
@@ -53,24 +49,6 @@ export function createEditorFrontstageProvider(): UiItemsProvider { | |||
icon: <SvgEdit />, | |||
}), | |||
], | |||
getToolbarItems: () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this removed?
@@ -102,7 +102,10 @@ | |||
"@itwin/itwinui-icons-react": "^2.8.0", | |||
"@itwin/itwinui-layouts-react": "~0.4.1", | |||
"@itwin/itwinui-layouts-css": "~0.4.0", | |||
"@itwin/presentation-backend": "4.4.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these presentation
dependencies necessary to showcase the new editors? Should we simply create a new frontstage for editors and use tool settings/dummy data in one of widgets?
Need to think about names, current EditorFrontstage
is really for opening iModels in read-write.
(I see the comment now, that this is actually planned to be removed)
@@ -92,6 +92,8 @@ export { | |||
} from "./components-react/datepicker/DatePickerPopupButton.js"; | |||
export { IntlFormatter } from "./components-react/datepicker/IntlFormatter.js"; | |||
|
|||
export { EditorInterop } from "./components-react/newEditors/interop/EditorInterop.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be exported here if tagged as @internal
.
@@ -92,6 +92,8 @@ export { | |||
} from "./components-react/datepicker/DatePickerPopupButton.js"; | |||
export { IntlFormatter } from "./components-react/datepicker/IntlFormatter.js"; | |||
|
|||
export { EditorInterop } from "./components-react/newEditors/interop/EditorInterop.js"; | |||
export * from "./components-react/newEditors/index.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid the nested barrel files and especially star re-exports (due to a higher chance of unexpected re-export). Simply inline what's exported here explicitly.
Additionally, I think we should start with only exporting editor components (i.e. useBooleanEditorProps
should be marked as @internal
).
export const editorsRegistryContext = React.createContext<EditorsRegistry>({ | ||
editors: [], | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const editorsRegistryContext = React.createContext<EditorsRegistry>({ | |
editors: [], | |
}); | |
export const EditorsRegistryContext = React.createContext<EditorsRegistry>({ | |
editors: [], | |
}); | |
EditorsRegistryContext.displayName = "uifw:EditorsRegistryContext"; |
import type { Value } from "./values/Values.js"; | ||
import type { EditorProps } from "./Types.js"; | ||
|
||
type CommittingEditorProps = Omit<EditorProps, "onFinish" | "onChange"> & { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means, that CommitingEditor
is not a "real editor" and can not be provided via EditorsRegistryProvider
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nvm, I see this is only adding some keyboard handlers for the regulard Editor
.
/** | ||
* A type that makes a specific properties required in a type. | ||
* @beta | ||
*/ | ||
export type RequiredProps<TProps, TKey extends keyof TProps> = Omit< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* A type that makes a specific properties required in a type. | |
* @beta | |
*/ | |
export type RequiredProps<TProps, TKey extends keyof TProps> = Omit< | |
/** | |
* A type that makes a specific properties required in a type. | |
* @internal | |
*/ | |
export type RequiredProps<TProps, TKey extends keyof TProps> = Omit< |
onChange: (value: TValue) => void; | ||
onFinish: () => void; | ||
disabled?: boolean; | ||
size?: "small" | "large"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size
should probably not be controlled per editor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed most of the stuff in components-react
and imodel-components-react
.
metadata: ValueMetadata; | ||
value?: TValue; | ||
onChange: (value: TValue) => void; | ||
onFinish: () => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear what onFinish
does without looking at the code. Maybe it should be renamed? Or, at least it needs clear docs.
* @beta | ||
*/ | ||
export interface InstanceKeyValue { | ||
key: { id: string; className: string }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using Id64String
over string
makes it more clear what we expect there.
key: { id: string; className: string }; | |
key: { id: Id64String; className: string }; |
* Type guard for text value. | ||
* @beta | ||
*/ | ||
export function isTextValue( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe it makes sense to put these type guards under Value
namespace?
| "instanceKey"; | ||
|
||
/** | ||
* Additional metadata that is used along side value to determine applicable editor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to mention "editor" in docs of these types? I could see them being used in non-editing contexts, e.g. read-only property rendering use cases
*/ | ||
export interface EnumValue { | ||
choice: number | string; | ||
label: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does enum value need to have a label? I think it could be retrieved from metadata based on choice
. Also, maybe choice
should be renamed to value
?
* Additional metadata that is used along side value to determine applicable editor. | ||
* @beta | ||
*/ | ||
export interface ValueMetadata { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this should also have information if the value is nullable? in our case most properties will be nullable, but some won't (e.g. some navigation properties like BisCore:Element.Model
)
const defaultValue = | ||
choices.length > 0 | ||
? { choice: choices[0].value, label: choices[0].label } | ||
: { choice: 0, label: "" }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe in this case the choice
should be empty string instead of 0
?
import { isTextValue } from "../../values/Values.js"; | ||
|
||
/** | ||
* Hooks that converts generic `EditorProps` into editor props with text value. If value is not text, it will be converted into empty text value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If value is not text, it will be converted into empty text value.
Maybe we should attempt to convert the value to its text representation instead?
displayValue: | ||
initialValue !== undefined ? formatValue(initialValue) : "", | ||
}, | ||
placeholder: formatValue(123.45), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe use initialValue
as the placeholder, if it's provided?
* @beta | ||
*/ | ||
export interface ColorValueMetadata extends ValueMetadata { | ||
params: ColorEditorParams[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this an array?
Proposal for new editor system
Proposal for the new editors system. This would replace existing editors system in order to simplify requirements for custom editors. Also it removes static EditorManager in favor of editors registry controlled through React context.
API overview:
EditorsRegistry - Application should use it to register their on custom editors or custom editors provided by some dependencies so that they would be accessible to all components in the application that uses editors system:
EditorsRegistryProvider
- adds supplied editors to the registry hold inReact
context. It supports nesting multipleEditorsRegistryProvider
to allow registering custom editors specific for some component that have higher priority than the ones registered near the root of the application.useEditor
- hook to get the editor that should be used to edit the supplied value. First it looks for applicable editor in EditorsRegistry and if none was found it fallbacks to the default editors.Editor
(name subject to change) - wrapper around EditorRegistry that provides a convenient way to render editors for specific value.CommitingEditor
(name subject to change) - wrapper aroundEditor
that works as uncontrolled component that commits value onEnter
or when editor completes value change (e.g. Toggle click). Also allows to cancel editing onEsc
.Value
- type for all values that are supported by editors.ValueMetadata
- type for additional metadata that can be supplied to editors alongside value itself. It can be extended when implementing custom editors. (E.g. passing available choices and icons to the enum editor that is rendered as button group)Default editors and accompanying
use<EditorName>Props
hooks that acts as a type guard to conveniently convert from generalEditorProps
to the specific editor props.EditorInterop
- internal implementation that mapsPropertyRecord
to the newValue
andValueMetadata
types. It is needed to support existing usage on the editors.Units supports
New editors system was aimed to provide better support for units. There is base component that should help with that
FormattedNumericInput
. It should be easy to write an editor on top of it that would know how to findParser
/Formatter
for specific unit. E.g: https://github.com/iTwin/appui/tree/editors/new-system/ui/imodel-components-react/src/imodel-components-react/inputs/newEditorsCustom editors
The goal of the new editors system is to remove the need for static editor registration and provide more convenient API for implementing custom editors. Current API has quite a lot optional properties that do not make sense (
propertyRecord
is optional but if it isundefined
there is no way to figure out what to render):Example of custom editor using old editor system and react class components:
Custom editor using old system and react functional components:
Custom boolean editor using new system:
The new system removes all the code that was associated with class components and accessing values through editor
ref
. It is not clear if that was used/useful so the chosen approach is to add something similar later if that is still needed. Majority of that was used byEditorContainer
that is represented byCommitingEditor
in the new system.TODO
PropertyEditorParams
fromPropertyRecord
. Need to find a way how to sunset thosePropertyEditorParams
in the future but in mean time if should be possible to maintain what is already there in the old system.PropertyEditorParams
. The initial approach is to maintains internal registry (iconName: string) => ReactNode that would hold currently used icons. Open for suggestions on this one.CommitingEditor
as general solutions for committing entered values only on Enter/Blur or each components should have it's own logic to handle such workflows?